'abuframework.monkey by Belimoth

'TODO
'	ability to poll input at a higher frequency than the update rate
'	camera filtering
'	change PreStart:Void to PreStart:Bool for preloading behavior? or something

Strict
Import mojo

Import abutime
Import abuinput
Import abuanimation
Import abuentity
Import abuschedule



'static class that contains framework-related global variables and singletons
Class Abu
	Global game:AbuApp
	Global engine:AbuEngine
	Global stage:AbuStage
	Global display:AbuVirtualDisplay
	Global cameras:List<AbuCamera>
	
	Global borderColor:Int[] = [0, 0, 0]
	
	Function Add:Void(actor:IRender)
		stage.Add(actor)
	End
	
	Function Remove:Void(actor:IRender)
		stage.Remove(actor)
	End
	
	Function Update:Void()
		engine.Update()
	End
	
	Function Render:Void()
		engine.Render()
	End
End



Class _AbuApp Extends App
	Field parent:AbuApp
	
	Method New(parent:AbuApp)
		Self.parent = parent
	End
	
	Method OnCreate:Int()
		parent.OnCreate()
		Return 0
	End
	
	Method OnUpdate:Int()
		parent.OnUpdate()
		Return 0
	End
	
	Method OnSuspend:Int()
		parent.OnSuspend()
		Return 0
	End
	
	Method OnResume:Int()
		parent.OnResume()
		Return 0
	End
	
	Method OnRender:Int()
		parent.OnRender()
		Return 0
	End
	
	Method OnLoading:Int()
		parent.OnLoading()
		Return 0
	End
End



Class AbuApp

Private
	Field _app:_AbuApp
	'Field FPS:Int = 60
	
	''Field textureManager:AbuTextureManager
	Field stateCurrent:AbuState
	Field stateNext:AbuState
	
Public
	Method New()
		_app = New _AbuApp(Self)	
	End
	
	Method OnCreate:Void()
		Abu.game = Self
		Abu.engine = New AbuEngine()
		Abu.stage = New AbuStage()
		Abu.display = New AbuVirtualDisplay( DeviceWidth(), DeviceHeight() )
		Abu.cameras = New List<AbuCamera>
		Abu.cameras.AddLast( New AbuCamera() )
		SetUpdateRate(60)
	End
	
	Method OnUpdate:Void()
		stateCurrent.OnUpdate()
		'If fader.active
		'	fader.Update()
		'Endif
	End
	
	Method OnSuspend:Void()
		
	End
	
	Method OnResume:Void()
		
	End
	
	Method OnRender:Void()
		stateCurrent.PreRender()
		Cls()
		stateCurrent.OnRender()
	End
	
	Method OnLoading:Void()
		
	End
	
	Method SetState:Void(stateNew:AbuState)
		'stateCurrent.Finalize()
		stateCurrent = stateNew
		stateCurrent.OnStart()
	End
End



Class AbuState
	Method PreStart:Void()			'things to do before fade-in, like loading assets
	
	End
	
	Method OnStart:Void()
	
	End
	
	Method OnUpdate:Void()
	
	End
	
	Method PreRender:Void()
	
	End
	
	Method OnRender:Void()
	
	End
End



Class AbuEngine
	Method Update:Void()
		UpdateTime()
		UpdateInput()
		UpdateAnimations()
		UpdateVelocities()
		UpdatePhysics()
		UpdateSchedules()
		UpdateEntities()
	End
	
	Method Render:Void()
		'TODO parallel rendering of cameras when possible
		AbuCamera.SortCameras()
		Abu.display.Update()
		For Local camera := EachIn Abu.cameras
			camera.Render()
		Next
	End
End



Class AbuRenderGroup Implements IRender
	Field members := New List<IRender>
	
	Method Visible:Bool() Property Return _visible End
	Method Visible:Void(visible:Bool) Property _visible = visible End
	Method Alpha:Float() Property Return _alpha End
	Method Alpha:Void(alpha:Float) Property _alpha = alpha End
	Method ZIndex:Int() Property Return _zIndex End
	Method ZIndex:Void(zIndex:Int) Property _zIndex = zIndex End
	
	Method Add:Void(member:IRender)
		members.AddLast(member)
	End
	
	Method Remove:Void(member:IRender)
		members.RemoveEach(member)
	End
	
	Method Render:Void()
		For Local member := EachIn members
			member.Render()
		Next
	End
Private
	Field _visible:Bool = True
	Field _alpha:Float = 1
	Field _zIndex:Int
End



Class AbuStage
	Field actors := New List<IRender>
	Field zSort:Bool = False			'not implemented yet
	
	Method New()
		Abu.stage = Self
	End
	
	Method Add:list.Node<IRender>(actor:IRender)
		Return actors.AddLast(actor)
	End
	
	Method Remove:Void(actor:IRender)
		actors.RemoveEach(actor)
	End
	
	Method Render:Void()
		For Local actor := EachIn actors
			actor.Render()
		Next
	End
End



Class AbuCamera
	Method X:Float() Property Return _xVirtual; End
	Method X:Void(x:Float) Property _xVirtual = x; End
	Method Y:Float() Property Return _yVirtual; End
	Method Y:Void(y:Float) Property _yVirtual = y; End
	Method Width:Float() Property Return _widthVirtual; End
	Method Width:Void(width:Float) Property _widthVirtual = width; End
	Method Height:Float() Property Return _heightVirtual; End
	Method Height:Void(height:Float) Property _heightVirtual = height; End
	
	Field xTarget:Float, yTarget:Float
	Field zoom:Float = 1.0
	Field rotation:Float
	
	Field backgroundColor:Int[] = [0, 0, 0]
	
	Method New()
		_widthVirtual = DeviceWidth()
		_heightVirtual = DeviceHeight()
		xTarget = _widthVirtual * 0.5
		yTarget = _heightVirtual * 0.5
	End
	
	Method New(x:Int, y:Int, width:Int, height:Int)
		_xVirtual = x
		_yVirtual = y
		_widthVirtual = width
		_heightVirtual = height
		Update()
	End
	
	Method FitToDisplay:Void()
		Local display:AbuVirtualDisplay = Abu.display
		_widthVirtual = display.widthVirtual
		_heightVirtual = display.heightVirtual
		xTarget = _widthVirtual * 0.5
		yTarget = _heightVirtual * 0.5
		Update()
	End
	
	Method Update:Void()
		Local display:AbuVirtualDisplay = Abu.display
		_widthActual = _widthVirtual * display.multiplier
		_heightActual = _heightVirtual * display.multiplier
		_xActual = _xVirtual * display.multiplier + display.xActual
		_yActual = _yVirtual * display.multiplier + display.yActual
	End
	
	Method Render:Void()
		Update()	'TEMPORARY?
		
		SetMatrix 1, 0, 0, 1, 0 ,0
		Translate Abu.display.xActual, Abu.display.yActual
		Scale Abu.display.multiplier * zoom, Abu.display.multiplier * zoom
		Translate (_xVirtual + _widthVirtual * 0.5) / zoom, (_yVirtual + _heightVirtual * 0.5) / zoom 
		Rotate rotation
		Translate -xTarget, -yTarget
		SetScissor _xActual, _yActual, _widthActual, _heightActual
		Cls backgroundColor[0], backgroundColor[1], backgroundColor[2]
		Abu.stage.Render()
		
		'SetColor 255, 255, 255
		'SetMatrix 1, 0, 0, 1, 0 ,0
		'Translate xVirtual * Abu.display.multiplier * zoom, yVirtual * Abu.display.multiplier * zoom
		'DrawText _tier, 0, 0
		
		'PopMatrix()
		'SetScissor scissor[0], scissor[1], scissor[2], scissor[3]
	End
	
	Function SortCameras:Void()
		If Abu.cameras.Count() > 1
			Local arr:AbuCamera[] = Abu.cameras.ToArray()
			For Local cam1:Int = 1 Until arr.Length
				For Local cam0:Int = 0 Until cam1
					Local c0:AbuCamera = arr[cam0]
					Local c1:AbuCamera = arr[cam1]
					If _RectanglesOverlap(c0._xVirtual, c0._yVirtual, c0._widthVirtual, c0._heightVirtual, c1._xVirtual, c1._yVirtual, c1._widthVirtual, c1._heightVirtual)
						c1._parent = c0
					EndIf
				Next
			Next
			
			For Local camera := EachIn Abu.cameras
				camera._tier = 0
				If camera._parent <> Null Then camera._tier = camera._parent._tier + 1
			Next
		EndIf
	End
	
Private
	Field _parent:AbuCamera, _tier:Int
	
	Field _xVirtual:Float, _yVirtual:Float
	Field _widthVirtual:Float, _heightVirtual:Float
	
	Field _xActual:Float, _yActual:Float
	Field _widthActual:Float, _heightActual:Float
	
	
	Function _RectanglesOverlap:Bool(x0:Int, y0:Int, w0:Int, h0:Int, x1:Int, y1:Int, w1:Int, h1:Int)
		Local xx0:Int = Max(x0, x1)
		Local yy0:Int = Max(y0, y1)
		Local xx1:Int = Min(x0 + w0 - 1, x1 + w1 - 1)
		Local yy1:Int = Min(y0 + h0 - 1, y1 + h1 - 1)
		If xx1 - xx0 > 0 And yy1 - yy0 > 0 Then Return True
		Return False
	End
End



Function SetDisplay:Void(width:Int, height:Int)
	'TODO	
End



'TODO move these into Abu class?
Function SetVirtualDisplay:Void(width:Int, height:Int)
	Abu.display = New AbuVirtualDisplay(width, height)
	Abu.cameras.First().FitToDisplay()
	For Local camera := EachIn Abu.cameras
		If camera <> Abu.cameras.First()
			camera.Update()
		EndIf
	Next
End




Function VirtualMouseX:Float(camera:AbuCamera = Null)			'untested
	If camera = Null Then camera = Abu.cameras.First()
	Return (Float( MouseX() ) - camera._xActual) / (Abu.display.multiplier * camera.zoom)
End



Function VirtualMouseY:Float(camera:AbuCamera = Null)			'untested
	If camera = Null Then camera = Abu.cameras.First()
	Return (Float( MouseY() ) - camera._yActual) / (Abu.display.multiplier * camera.zoom)
End



'Private

Class AbuVirtualDisplay
	Field widthDevice:Float, heightDevice:Float
	Field ratioDevice:Float
	
	Field widthVirtual:Float, heightVirtual:Float
	Field ratioVirtual:Float
	
	Field widthActual:Float, heightActual:Float
	Field multiplier:Float
	
	Field xActual:Float, yActual:Float
	Field xVirtual:Float, yVirtual:Float
	
	Method New(width:Int, height:Int)
		widthVirtual = width
		heightVirtual = height
		ratioVirtual = heightVirtual / widthVirtual
		_Update()
	End
	
	Method Update:Void(cls:Bool = True)
		SetMatrix 1, 0, 0, 1, 0, 0
		
		If ( widthDevice <> Float( DeviceWidth() ) ) Or ( heightDevice <> Float( DeviceHeight() ) ) Then _Update()
		
		SetScissor 0, 0, DeviceWidth(), DeviceHeight()
		If cls Then Cls Abu.borderColor[0], Abu.borderColor[1], Abu.borderColor[2]
	End
	
	Method _Update:Void()
		widthDevice = Float( DeviceWidth() )
		heightDevice = Float( DeviceHeight() )
		
		ratioDevice = heightDevice / widthDevice
		
		If ratioDevice >= ratioVirtual
			multiplier = widthDevice / widthVirtual
			
			widthActual = widthVirtual * multiplier
			heightActual = heightVirtual * multiplier
			
			xActual = 0
			yActual = (heightDevice - heightActual) * 0.5
		Else
			multiplier = heightDevice / heightVirtual
			
			widthActual = widthVirtual * multiplier
			heightActual = heightVirtual * multiplier
			
			xActual = (widthDevice - widthActual) * 0.5
			yActual = 0
		EndIf
		
		xVirtual = xActual / multiplier
		yVirtual = yActual / multiplier
	End
End